/*  signals.c: signal handling */

/*  This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License , or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; see the file COPYING.  If not, write to
    the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.

    You may contact the author by:
    e-mail:  hanslub42@gmail.com
*/

#include "rlwrap.h"

volatile int command_is_dead = FALSE;
int commands_exit_status = 0;
int filter_is_dead = FALSE;
int filters_exit_status = 0;
int deferred_adapt_commands_window_size = FALSE; /* whether we have to adapt clients winsize when accepting a line */
int signal_handlers_were_installed = FALSE; 
int received_sigALRM =  FALSE;

static void change_signalmask(int, int *);
static void child_died(int);
static void pass_on_signal(int);
static void handle_sigTSTP(int);



int adapt_tty_winsize(int, int);
static void wipe_textarea(struct winsize *old_size);
static int signals_program_error(int signal);

static int signals_to_be_passed_on[] = {
  SIGHUP, SIGINT,
  SIGQUIT, SIGTERM, SIGCONT, SIGUSR1,
  SIGUSR2, SIGWINCH, 0
};

#ifndef MAX_SIG /* is this POSIX? couldn't find it, so: */
#define MAX_SIG 100
#endif


#ifdef DEBUG
static void log_named_signal(int);
#else
static void handle_program_error_signal(int);
#endif



int sigterm_received = FALSE;


void
mysignal(int sig, sighandler_type handler, const char *handler_name) {
  
#ifdef HAVE_SIGACTION
  struct sigaction action;
  if (handler == SIG_DFL)
    DPRINTF2(DEBUG_SIGNALS,"Re-setting handler for signal %d (%s) to its default", sig, signal_name(sig));
  else if (handler == SIG_IGN)
    DPRINTF2(DEBUG_SIGNALS,"Ignoring signal %d (%s)", sig, signal_name(sig)); 
  else
    DPRINTF3(DEBUG_SIGNALS,"Setting handler for signal %d (%s) to %s()", sig, signal_name(sig), handler_name);
    
  action.sa_handler = handler;
  sigfillset(&action.sa_mask); /* don't bother making our signal handlers re-entrant (they aren't) */
  action.sa_flags = (sig == SIGCHLD ? SA_NOCLDSTOP : 0); /* no SA_RESTART */
  if (sigaction(sig, &action, NULL) != 0)
# else /* rlwrap running in Ye Olde Computer Museum?? */
  if (signal(sig, handler) == SIG_ERR)
# endif
    if(handler != SIG_DFL) /* allow e.g. KILL to be set to its default */ 
      myerror(FATAL|USE_ERRNO, "Failed setting handler for signal %d (%s)", sig, signal_name(sig));   
}


void
install_signal_handlers()
{
  int i;
  mysignal(SIGCHLD, HANDLER(child_died));
  mysignal(SIGTSTP, HANDLER(handle_sigTSTP));
#ifndef DEBUG          /* we want core dumps when debugging, no polite excuses! */
  for (i = 1; i<MAX_SIG; i++)
    if (signals_program_error(i))
      mysignal(i, HANDLER(handle_program_error_signal)); /* make polite excuse */
#endif
  mysignal(SIGALRM, HANDLER(handle_sigALRM));
  for (i = 1;  signals_to_be_passed_on[i]; i++) {
    assert(!signals_program_error(signals_to_be_passed_on[i]));
    mysignal(signals_to_be_passed_on[i], HANDLER(pass_on_signal));
  }   
  signal_handlers_were_installed = TRUE;
}


static void uninstall_signal_handlers() {
  int i;
  for(i=1; i<MAX_SIG; i++)
    mysignal(i, SIG_DFL, NULL);
  signal_handlers_were_installed = FALSE;
}


void
ignore_sigchld()
{
  mysignal(SIGCHLD, HANDLER(do_nothing));
}


/* we'll call signals whose handlers mess with readline's internal state "messy signals"
   at present SIGTSTP and SIGWINCH are considered "messy". The following 2 functions are used
   in main.c to block messy signals except when waiting for I/O */

void
block_signals(int *sigs)
{
  change_signalmask(SIG_BLOCK, sigs);
}



void
unblock_signals(int *sigs)
{
  change_signalmask(SIG_UNBLOCK, sigs);
} 


void
block_all_passed_on_signals(void)
{
  change_signalmask(SIG_BLOCK, signals_to_be_passed_on);
}

static void
change_signalmask(int how, int *sigs)
{                               /* sigs should point to a *zero-terminated* list of signals */
  int i;
  sigset_t mask;
  
  sigemptyset(&mask);
  for (i = 0; sigs[i]; i++) {
    DPRINTF2(DEBUG_SIGNALS, "%sblocking signal %s", how == SIG_UNBLOCK ? "un-" : "", signal_name(sigs[i]));
    sigaddset(&mask, sigs[i]);
  }     
  sigprocmask(how, &mask, NULL);
}


void
unblock_all_signals()
{
  sigset_t mask;
  sigfillset(&mask);
  sigprocmask(SIG_UNBLOCK, &mask, NULL);
} 

void
block_all_signals()
{
  sigset_t mask;
  int i;
  sigfillset(&mask); /* block all signals */
  for (i = 0; i<MAX_SIG; i++)
    if (signals_program_error(i))
      sigdelset(&mask, i);  /* ... except those that were generated by errors within rlwrap */
  sigprocmask(SIG_BLOCK, &mask, NULL);
}


static void
handle_sigTSTP(int signo)
{
  sigset_t all_signals;
  int error, saved_errno = errno;
  
  DEBUG_RANDOM_SLEEP;
  sigfillset(&all_signals);
  
  DPRINTF2(DEBUG_SIGNALS, "got %s, sending it to pgid %d", signal_name(signo), command_pid);
  zero_select_timeout();
  /* Hand the SIGTSTP down to command and its process group */
  if (command_pid && (error = kill(-command_pid, SIGTSTP))) {
    myerror(FATAL|USE_ERRNO, "Failed to deliver SIGTSTP");
  }

  if (within_line_edit)
    save_rl_state();

  
  mysignal(SIGTSTP, SIG_DFL, NULL);   /* reset disposition to default (i.e. suspend) */
  sigprocmask(SIG_UNBLOCK, &all_signals, NULL); /* respond to sleep- and wake-up signals  */  
  kill(getpid(), SIGTSTP); /* suspend */
  /* keyboard gathers dust, kingdoms crumble,.... */

  /* .... */

  /* Beautiful princess types "fg", (or her father tries to kill us...) and we wake up HERE: */
  sigprocmask(SIG_BLOCK, &all_signals, NULL);
  mysignal(SIGTSTP, HANDLER(handle_sigTSTP)); 
  DPRINTF0(DEBUG_SIGNALS, "woken up");

  /* On most systems, command's process group will have been woken up by the handler of
     the signal that woke us up. This doesn't seem to happen for SIGCONT on QNX, so here goes: */

  #ifdef __QNX__
  if (command_pid && (error = kill(-command_pid, SIGCONT))) {
    myerror(FATAL|USE_ERRNO, "Failed to deliver SIGCONT");
  }
  #endif
  
  if (within_line_edit) {
    restore_rl_state();
  } else {
    set_echo(FALSE);
    cr();
    if (skip_rlwrap())
      return;
    move_cursor_to_start_of_prompt(ERASE);
    cook_prompt_if_necessary();
    my_putstr(saved_rl_state.cooked_prompt);
  }
  adapt_tty_winsize(STDIN_FILENO, master_pty_fd);       /* just in case */
  errno = saved_errno;
  sigprocmask(SIG_UNBLOCK, &all_signals, NULL);
}

/* signal handler, passes the signal to the child process group
   SIGWINCH is only passed after calling adapt_tty_winsize()
*/

static void
pass_on_signal(int signo)
{
  int ret, saved_errno = errno, pass_it_on = TRUE;
  char signo_as_str[4];
  
  DEBUG_RANDOM_SLEEP;
  zero_select_timeout();
#ifdef DEBUG
  log_named_signal(signo);
#endif
  
  if(pass_on_sigINT_as_sigTERM && signo == SIGINT)
    signo=SIGTERM;

  assert(MAX_SIG < 1000);  errno = 0 ; /* prevent overflow of sprintf and underflow of strtol on the next line */
  sprintf(signo_as_str, "%d", signo);
  signo = strtol(pass_through_filter(TAG_SIGNAL, signo_as_str), NULL, 10);

  switch (signo) {
  case SIGWINCH: /* non-POSIX, but present on most systems */
    /* Make slave pty's winsize equal to that of STDIN. Pass the signal on *only if*
       winsize has changed. This is particularly important because we have the slave pty
       still open - when we pass on the signal the child will probably do a TIOCSWINSZ ioctl()
       on the slave pty  - causing us (the parent) to get a SIGWINCH again, thus getting stuck in a loop */
    pass_it_on = adapt_tty_winsize(STDIN_FILENO, master_pty_fd);
    break;
  case SIGTERM:
    sigterm_received = TRUE;
    break;
  default:
    break;
  }
  if (!command_pid)
    pass_it_on = FALSE;         /* no child (yet) to pass it on to */

  if (pass_it_on) {             /* we resend the signal to the process *group* of the child */
    ret = kill( -command_pid, signo);
    DPRINTF3(DEBUG_SIGNALS, "kill(%d,%s) = %d", -command_pid, signal_name(signo), ret);
    we_just_got_a_signal_or_EOF = TRUE; /* signal to main loop that next command output should leave the prompt alone */ 
  }
  errno = saved_errno;
  
}


/* This function handles a terminal resize by copying from_fd's winsize
   to that of to_fd, keeping displayed line tidy, if possible
   (i.e. if terminal is capable enough, and we know enough about
   readlines internals).  A return value != 0 means that the size has
   changed.
*/

int
adapt_tty_winsize(int from_fd, int to_fd)
{
  int ret;

  struct winsize old_winsize = winsize;

  ret = ioctl(from_fd, TIOCGWINSZ, &winsize);
  DPRINTF1(DEBUG_SIGNALS, "ioctl (..., TIOCGWINSZ) = %d", ret);
  if (winsize.ws_col != old_winsize.ws_col || winsize.ws_row != old_winsize.ws_row) { 
    DPRINTF4(DEBUG_SIGNALS, "ws.col: %d -> %d, ws.row: %d -> %d", old_winsize.ws_col, winsize.ws_col, old_winsize.ws_row, winsize.ws_row);
    if (always_readline &&!dont_wrap_command_waits())  /* if --always_readline option is set, the client will probably spew a */
      deferred_adapt_commands_window_size = TRUE;      /* volley of control chars at us when its terminal is resized. Hence we don't do it now */
    else {  
      ret = ioctl(to_fd, TIOCSWINSZ, &winsize); 
      DPRINTF1(DEBUG_SIGNALS, "ioctl (..., TIOCSWINSZ) = %d", ret);
    }   
    DPRINTF2(DEBUG_READLINE, "rl_prompt: %s, line_buffer: %s", mangle_string_for_debug_log(rl_prompt, 100), rl_line_buffer);
    rl_set_screen_size(winsize.ws_row, winsize.ws_col); /* this is safe: we know that right now rlwrap is not calling
                                                           the readline library because we keep SIGWINCH blocked all the time */

    if (!within_line_edit && !skip_rlwrap()) {
      wipe_textarea(&old_winsize);
     
      received_WINCH = TRUE;           /* we can't start line edit in signal handler, so we only set a flag */
    } else if (within_line_edit) {      /* try to keep displayed line tidy */
      wipe_textarea(&old_winsize);
      rl_on_new_line();
      rl_redisplay();
      
    }
    
    return (!always_readline || dont_wrap_command_waits()); /* pass the signal on (except when always_readline is set and command is not waiting) */
  } else {                      /* window size has not changed */
    return FALSE;
  }
  
}

/* After a resize, clear all the lines that were occupied by prompt + line buffer before the resize */
static
void wipe_textarea(struct winsize *old_winsize)
{
  int point, lineheight, linelength, cursor_height, i, promptlength;
  if (!prompt_is_single_line()) {       /* Don't need to do anything in horizontal_scroll_mode  */
    promptlength = colourless_strlen((saved_rl_state.cooked_prompt ? saved_rl_state.cooked_prompt:  saved_rl_state.raw_prompt), NULL, old_winsize -> ws_col); 
    linelength = (within_line_edit ? strlen(rl_line_buffer) : 0) + promptlength;
    point = (within_line_edit ? rl_point : 0) + promptlength;
    assert(old_winsize -> ws_col > 0);
    lineheight = (linelength == 0 ? 0 : (1 + (max(point, (linelength - 1)) / old_winsize -> ws_col)));
    if (lineheight > 1 && term_cursor_up != NULL && term_cursor_down != NULL) {
      /* i.e. if we have multiple rows displayed, and we can clean them up first */
      cr(); 
      cursor_height = point / old_winsize -> ws_col;    /* cursor is still on old line */
      DPRINTF2(DEBUG_SIGNALS, "lineheight: %d, cursor_height: %d", lineheight, cursor_height);
      for (i = 1 + cursor_height; i < lineheight; i++)
        curs_down();    /* ...move it down to last line */
      for (i = lineheight; i > 1; i--) {        /* go up again, erasing every line */
        clear_line(); 
        curs_up();
      }
    }
    clear_line();
    cr();
  }
}       

static void
child_died(int UNUSED(signo))
{
  int saved_errno;
  DEBUG_RANDOM_SLEEP;
  zero_select_timeout();
  saved_errno = errno;
  DPRINTF0(DEBUG_SIGNALS, "Caught SIGCHLD");
  
  if(command_pid && waitpid(command_pid, &commands_exit_status, WNOHANG)) {
    DPRINTF2(DEBUG_SIGNALS, "child (pid %d) has died, exit status: %x", command_pid, commands_exit_status);
    command_is_dead = TRUE;
    command_pid = 0;            /* thus we know that there is no child anymore to pass signals to */
  } else if (filter_pid && waitpid(filter_pid, &filters_exit_status, WNOHANG)) { 
    DPRINTF2(DEBUG_SIGNALS, "filter (pid %d) has died, exit status: %x", filter_pid, filters_exit_status);
    filter_is_dead = TRUE;
    filter_pid = 0;
  } else  {
    DPRINTF0(DEBUG_ALL, "Whoa, got a SIGCHLD, but not from slave command or filter! I must have children I don't know about (blush...)!");
    /* ignore */
  }     

  errno = saved_errno;  
  return;   /* allow remaining output from child to be processed in main loop */
            /* (so that we will see childs good-bye talk)                     */
            /* this will then clean up and terminate                          */

}


#ifdef DEBUG
static void
log_named_signal(int signo)
{
  if (debug) 
    DPRINTF1(DEBUG_SIGNALS, "got %s", signal_name(signo));  
}
#else
static void
handle_program_error_signal(int sig)
{  /* Even after sudden and unexpected death, leave the terminal in a tidy state */
  int res;
  
  printf("\n%s: Oops, crashed (caught %s) - this should not have happened!\n"
         "If you need a core dump, re-configure with --enable-debug and rebuild\n"
         "Resetting terminal and cleaning up...\n", program_name, signal_name(sig));
  if (colour_the_prompt || filter_pid)
    res = write(STDOUT_FILENO,"\033[0m",4); /* reset terminal colours */
  if (terminal_settings_saved)
    tcsetattr(STDIN_FILENO, TCSAFLUSH, &saved_terminal_settings);
 
  exit(EXIT_FAILURE);
}
#endif


static int coredump(int status) {
#ifdef WCOREDUMP
  return WCOREDUMP(status);
#else
  return 0;
#endif
}

void suicide_by(int signal, int status) {
  /* Some signals suggest a program error. When rlwrap kills itself with one of those,
     the shell may tell the user that rlwrap itself has failed. Make clear that
     it didn't. @@@ We could also try argv[0] = command_name just before dying ? */
  
  if (signals_program_error(signal)) {  
    myerror(WARNING|NOERRNO, "%s crashed, killed by %s%s.\n%s itself has not crashed, but for transparency,\n"
           "it will now kill itself %swith the same signal\n",
           command_name, signal_name(signal), (coredump(status) ? " (core dumped)" : ""),
           program_name, (coredump(status) ? "" : "(without dumping core) ") );
  }
  uninstall_signal_handlers();
  unblock_all_signals();
  set_ulimit(RLIMIT_CORE,0); /* prevent our own useless core dump from clobbering the interesting one created by command */
  DPRINTF1(DEBUG_SIGNALS, "suicide by signal %s", signal_name(signal));
  kill(getpid(), signal);
  /* still alive? */
  sleep(1);
  exit(0);
}

static int myalarm_was_set = FALSE;


/* drop-in replacement for alarm (*but* with arg in msecs, not secs). Also sets global flag
   myalarm_was_set */

void myalarm(int msecs) {
#ifdef HAVE_SETITIMER
  int retval;
  struct itimerval awhile = {{0,0},{0,0}};
  int secs = msecs/1000;
  awhile.it_value.tv_sec = secs;
  awhile.it_value.tv_usec = (msecs -  secs * 1000) * 1000;
  received_sigALRM = FALSE;
  retval = setitimer(ITIMER_REAL, &awhile, NULL);
  DPRINTF3(DEBUG_AD_HOC, "setitimer() = %d (tv_sec = %d, tv_usec=%ld)", retval, secs, awhile.it_value.tv_usec);
#else
  received_sigALRM = FALSE;
  alarm(msecs == 0 ? 0 : 1 + msecs/1000)); 
#endif
DPRINTF1(DEBUG_AD_HOC, "set alarm (%d msecs)", msecs);
if (msecs == 0)
  return;
myalarm_was_set = TRUE;
}

void
handle_sigALRM(int UNUSED(signo)) {
  received_sigALRM = TRUE;
  assert(myalarm_was_set); /* cry wolf if sigALRM is caught when none was requested by myalarm */
  myalarm_was_set= FALSE;
  DPRINTF0(DEBUG_SIGNALS, "got sigALRM");
}


/* Give name of signal. A case() construct is not appropriate here as on some
   architectures signal values may coincide */
char *signal_name(int signal) {
  return
    signal ==  SIGHUP  ? "SIGHUP" :
    signal ==  SIGINT  ? "SIGINT" :
    signal ==  SIGQUIT  ? "SIGQUIT" :
    signal ==  SIGILL  ? "SIGILL" :
    signal ==  SIGABRT  ? "SIGABRT" :
    signal ==  SIGTRAP  ? "SIGTRAP" :
#ifdef SIGIOT  /* 4.2 BSD (IOT trap ) */
    signal ==  SIGIOT  ? "SIGIOT" :
#endif
#ifdef SIGEMT  /* 4.2 BSD (EMT trap ) */
    signal ==  SIGEMT  ? "SIGEMT" :
#endif
    signal ==  SIGFPE  ? "SIGFPE" :
    signal ==  SIGKILL  ? "SIGKILL" :
#ifdef SIGBUS  /* 4.2 BSD (Bus error ) */
    signal ==  SIGBUS  ? "SIGBUS" :
#endif
    signal ==  SIGSEGV  ? "SIGSEGV" :
#ifdef SIGSYS  /* 4.2 BSD (Bad argument to system call ) */
    signal ==  SIGSYS  ? "SIGSYS" :
#endif
    signal ==  SIGPIPE  ? "SIGPIPE" :
    signal ==  SIGALRM  ? "SIGALRM" :
    signal ==  SIGTERM  ? "SIGTERM" :
    signal ==  SIGUSR1  ? "SIGUSR1" :
    signal ==  SIGUSR2  ? "SIGUSR2" :
    signal ==  SIGCHLD  ? "SIGCHLD" :
    signal ==  SIGSTOP  ? "SIGSTOP" :
    signal ==  SIGTSTP  ? "SIGTSTP" :
    signal ==  SIGCONT  ? "SIGCONT" :
#ifdef SIGCLD  /* System V (Same as SIGCHLD ) */
    signal ==  SIGCLD  ? "SIGCLD" :
#endif
#ifdef SIGPWR  /* System V (Power failure restart ) */
    signal ==  SIGPWR  ? "SIGPWR" :
#endif
    signal ==  SIGXCPU  ? "SIGXCPU" :
    signal ==  SIGXFSZ  ? "SIGXFSZ" :
    signal ==  SIGWINCH ? "SIGWINCH" : /* non-POSIX, but present on most systems */
    as_string(signal);
}


static int signals_program_error(int signal) {
  return
    signal ==  SIGILL   ||
    signal ==  SIGABRT  ||
    signal ==  SIGTRAP  ||
#ifdef SIGIOT  /* 4.2 BSD (IOT trap ) */
    signal ==  SIGIOT  ||
#endif
#ifdef SIGEMT  /* 4.2 BSD (EMT trap ) */
    signal ==  SIGEMT  ||
#endif
    signal ==  SIGFPE  ||
#ifdef SIGBUS  /* 4.2 BSD (Bus error ) */
    signal ==  SIGBUS  ||
#endif
    signal ==  SIGSEGV  ||
#ifdef SIGSYS  /* 4.2 BSD (Bad argument to system call ) */
    signal ==  SIGSYS  ||
#endif
    signal ==  SIGXCPU ||
    signal ==  SIGXFSZ ||
    FALSE ? TRUE : FALSE;
}       

